
#include "socket_io.h"
#include "logger.h"

#include <string>
#include <sstream>
#include <stdint.h>
#include <netdb.h>

//#define MAX_READ_SAMPLES 160
#define MAX_READ_SAMPLES 3200

using namespace std;

const std::string CSocketIO::sIniSection = "SOCKET_IO";

CSocketIO::CSocketIO(NAudioFormats::SStreamInfo pStreamInfo)
		:mStreamInfo(pStreamInfo),
		mServerSocket(-1),
		mSocket(-1),
		mByteBuf(NULL),
		mByteCount(0)
{
}

CSocketIO::~CSocketIO()
{
	if(mSocket != -1) {
		close(mSocket);
	}
	if(mServerSocket != -1) {
		close(mServerSocket);
	}
}

int CSocketIO::init(CPropertiesReader *pConfig)
{
	if(!pConfig) {
		return -1;
	}
	string lStrValue = "";
	if(!pConfig->containsKey(sIniSection, "NAME")
	|| !pConfig->getValue(sIniSection, "NAME", lStrValue)) {
		printl(LL_ERR, __FILE__, "Config error: NAME [%s]\n", sIniSection.c_str());
		return -1;
	}
	struct hostent* lHostEntry = gethostbyname(lStrValue.c_str());
	if(!lHostEntry) {
		printl(LL_ERR, __FILE__, "Can't resolve %s\n", lStrValue.c_str());
		return -1;
	}
	printl(0, __FILE__, "Name: %s\n", lHostEntry->h_name);
	for(unsigned int i = 0; lHostEntry->h_addr_list[i]; i++) {
		printl(0, __FILE__, "IP: %s\n",
				inet_ntoa( *(struct in_addr*)(lHostEntry->h_addr_list[i])));
	}

	if(!pConfig->containsKey(sIniSection, "PORT")
	|| !pConfig->getValue(sIniSection, "PORT", lStrValue)) {
		printl(LL_ERR, __FILE__, "Config error: PORT [%s]\n", sIniSection.c_str());
		return -1;
	}
	uint16_t lPort(0);
	{
		stringstream ss;
		ss << lStrValue;
		ss >> lPort;
	}
	printl(0, __FILE__, "Port: %u\n", lPort);

	bool lServer(false);
	if(pConfig->containsKey(sIniSection, "SERVER")
	&& pConfig->getValue(sIniSection, "SERVER", lStrValue)) {
		if(lStrValue == "true"
		|| lStrValue == "True"
		|| lStrValue == "TRUE") {
			lServer = true;
		}
	}

	if(lServer) {
		printl(0, __FILE__, "Start server for CSocketIO\n");
		mServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if(mServerSocket == -1) {
			printl(LL_ERR, __FILE__, "Socket error\n");
			return -1;
		}

		mServerAddress.sin_family = AF_INET;
		mServerAddress.sin_addr.s_addr = *(in_addr_t*)(lHostEntry->h_addr_list[0]);
		mServerAddress.sin_port = htons(lPort);

		if(bind(mServerSocket, (struct sockaddr *)&mServerAddress, sizeof(struct sockaddr_in)) == -1) {
			printl(LL_ERR, __FILE__, "Binding error\n");
			return -1;
		}
		if(listen(mServerSocket, 1) == -1) {
			printl(LL_ERR, __FILE__, "Listing error\n");
			return -1;
		}

		socklen_t lLength = sizeof(struct sockaddr_in);

		printl(0, __FILE__, "Wait for client ...\n");
		mSocket = accept(mServerSocket, (struct sockaddr *)&mAddress, &lLength);
		if(mSocket == -1) {
			printl(LL_ERR, __FILE__, "Accept failed\n");
			return -1;
		}
		printl(0, __FILE__, "Client connected\n");
	}
	else {
		mSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if(mSocket == -1) {
			printl(LL_ERR, __FILE__, "Socket error\n");
			return -1;
		}

		mAddress.sin_family = AF_INET;
		mAddress.sin_addr.s_addr = *(in_addr_t*)(lHostEntry->h_addr_list[0]);
		mAddress.sin_port = htons(lPort);

		if(connect(mSocket, (struct sockaddr *)&mAddress, sizeof(struct sockaddr_in)) == -1) {
			printl(LL_ERR, __FILE__, "Connection error\n");
			return -1;
		}
	}

	// send sample-width, sample-rate and codec to other peer
	unsigned char lSampleWidth(mStreamInfo.mSamplesWidth);
	uint32_t lSampleRate(mStreamInfo.mSampleRate);
	unsigned char lCodec(mStreamInfo.mCodec);
	if(fullSend(&lSampleWidth, 1) != 1) {
		return -1;
	}
	lSampleRate = htonl(lSampleRate);
	if(fullSend(&lSampleRate, 4) != 4) {
		return -1;
	}
	if(fullSend(&lCodec, 1) != 1) {
		return -1;
	}

	// recv sample-width, sample-rate and codec from other peer
	// and compare with this one.
	if(fullRecv(&lSampleWidth, 1) != 1) {
		return -1;
	}
	if(fullRecv(&lSampleRate, 4) != 4) {
		return -1;
	}
	lSampleRate = ntohl(lSampleRate);
	if(fullRecv(&lCodec, 1) != 1) {
		return -1;
	}
	if(lSampleWidth != mStreamInfo.mSamplesWidth
	|| lSampleRate != mStreamInfo.mSampleRate
	|| lCodec != mStreamInfo.mCodec) {
		printl(LL_ERR, __FILE__, "Wrong stream infos\n");
		return -1;
	}

	return 0;
}

int CSocketIO::destroy()
{
	return 0;
}

int CSocketIO::read(void** pData, EAction& pAction, int pTimeout)
{
	uint32_t lPacketLength(0);
	if(fullRecv(&lPacketLength, 4) != 4) {
		return -1;
	}
	lPacketLength = ntohl(lPacketLength);
	void* lData = getByteBuffer(lPacketLength);

	if(lPacketLength == 0) {
		*pData = NULL;
		pAction = EXTRACT;
		return 0;
	}
	if(fullRecv(lData, lPacketLength) != lPacketLength) {
		return -1;
	}
	*pData = lData;
	pAction = EXTRACT;
	return lPacketLength;
}

int CSocketIO::write(void* pData, const EAction& pAction, int pLength)
{
	if(pAction != EMBED) {
		return -1;
	}
	if(pLength < 0) {
		return -1;
	}
	uint32_t lPacketLength(pLength);
	lPacketLength = htonl(lPacketLength);
	if(fullSend(&lPacketLength, 4) != 4) {
		return -1;
	}
	// pLength instead of lPacketLength because lPacketLength has network order!
	if(fullSend(pData, pLength) != pLength) {
		return -1;
	}
	return pLength;
}

NAudioFormats::SStreamInfo CSocketIO::getStreamInfo()
{
	return mStreamInfo;
}

uint32_t CSocketIO::fullRecv(void *pBuf, uint32_t pLength)
{
	return static_cast<uint32_t>(recv(mSocket, pBuf, pLength, MSG_WAITALL));
}

int CSocketIO::fullSend(const void *pBuf, int pLength)
{
	if(pLength == 0) {
		// no send is necessary
		return 0;
	}
	return send(mSocket, pBuf, pLength, MSG_NOSIGNAL);
}

void* CSocketIO::getByteBuffer(int pByteLength)
{
	if((!mByteBuf && mByteCount > 0)
	|| (mByteBuf && mByteCount <= 0)) {
		printl(LL_ERR, __FILE__, "Inconsistent byte buffer\n");
		return NULL;
	}
	if(pByteLength <= 0) {
		if(mByteBuf) {
			delete [] mByteBuf;
			mByteBuf = NULL;
			mByteCount = 0;
		}
		return NULL;
	}

	// check if current buffer is too small
	if(pByteLength > mByteCount) {
		delete [] mByteBuf;
		mByteBuf = new char [pByteLength];
		mByteCount = pByteLength;
	}
	return mByteBuf;
}
